Skip to content
lab components / Navigation

New side navigation

Allow users to explore content and features through a primary navigation structure with a clear visual hierarchy

This is a Lab component!

That means it doesn't satisfy our definition of done and may be changed or even deleted. For an exact status, please reach out to the Fancy team through the dev_fancy or ux_fancy channels.

import { NewSideNavigation } from "@siteimprove/fancylab";

#Examples

Ideal for applications with multiple sections and subsections, where a clear visual hierarchy is essential.

The side navigation consists of:

  • Links: Clickable text or icons leading to different content areas.
  • Icons: Visual representations of navigation items (especially important in the collapsed view).
  • Chevrons: Expandable/collapsible indicators for nested navigation levels.
  • Levels: Primary (top-level), secondary, and tertiary levels to organize content.
  • Collapsed view: Can be used to collapse the navigation in mobile or tablet views.

#Basic usage

This is a basic example, where we have selected an item under "data privacy" and the rest of the settings are at the default values.

Best practices:

  • The selected item should be visually distinct.
  • Navigation structure should mirror the content's logical hierarchy.
  • Ensure the collapsed view uses clear, recognizable icons for each top-level item.
  • Dashboards
  • Core Wins
  • Dynamic Content
  • Brand
Digital Certainty Index
Social Media Performance
Marketing Analytics
Explore more modules
Theme: agentic-ai-2025
// These states are kept outside the component because you would most likely want to persist // these in something like local storage to work well will page reloads. const [selectionId, setSelectionId] = useState<string | null>( "dataprivacy-personal-data-types-phone-number" ); const [viewState, setViewState] = useState<NewViewState>(); const [expandedItems, setExpandedItems] = useState<string[]>(); return ( <> <div style={{ height: "100vh" }}> <NewSideNavigation data-observe-key="side-nav" mainItems={data.sideNavigation} bottomItems={data.sideBottomNavigation} selectionId={selectionId} setSelectionId={setSelectionId} viewState={viewState} setViewState={setViewState} expandedItems={expandedItems} setExpandedItems={setExpandedItems} searchHotkey="M" /> </div> </> );

#Falling back to default expansion

This example shows how the component falls back to showing expanded nodes that match with the selection if the expansion sent into the props are not properly showing the selected item. In this case, we select an item under "data privacy", but the expansion is set to showing something under QA. In this case, it will fall back to expanding the the nodes to show the selected item.

  • Use Case: This behavior is important when the provided expansion state doesn't align with the user's selection. It ensures the user can easily see the context of their current selection.
  • Best Practice: Clearly indicate the selected item, even when the navigation falls back to a default state. This includes a subtle animation to smoothly transition between expansion states.
  • Dashboards
  • Core Wins
  • Dynamic Content
  • Brand
Digital Certainty Index
Social Media Performance
Marketing Analytics
Explore more modules
Theme: agentic-ai-2025
// These states are kept outside the component because you would most likely want to persist // these in something like local storage to work well will page reloads. const [selectionId, setSelectionId] = useState<string | null>( "dataprivacy-personal-data-types-phone-number" ); const [viewState, setViewState] = useState<NewViewState>(); const [expandedItems, setExpandedItems] = useState(["qa", "qa-spelling-v2"]); return ( <> <div style={{ height: "100vh" }}> <NewSideNavigation data-observe-key="side-nav" mainItems={data.sideNavigation} bottomItems={data.sideBottomNavigation} selectionId={selectionId} setSelectionId={setSelectionId} viewState={viewState} setViewState={setViewState} expandedItems={expandedItems} setExpandedItems={setExpandedItems} /> </div> </> );

#Using labels

Menu-items can have labels. They are rendered using the Badge component. The label can have an optional icon, text, and tooltip.

  • Use Case: To highlight new features, internal use or other contextual information.
  • Best Practice:
    • Keep labels concise and visually distinct (use the Badge component).
    • If using icons, choose universally recognizable ones.
    .
Theme: agentic-ai-2025
// These states are kept outside the component because you would most likely want to persist // these in something like local storage to work well will page reloads. const [selectionId, setSelectionId] = useState<string | null>(null); const [viewState, setViewState] = useState<NewViewState>(); const [expandedItems, setExpandedItems] = useState<string[]>(); // Copy and wrangle the basic example to show off how to set labels const nav: MainNavigationItem[] = data.sideNavigation.slice(0, 6); nav[0].labels = [{ type: "highlight1", icon: <IconFeatureFlag />, tooltip: "Feature Flag Xyz" }]; nav[1].labels = [{ type: "highlight3", text: "Internal", tooltip: "Internal use only" }]; nav[3].children![0].labels = [{ type: "highlight2", text: "Demo", tooltip: "Demo data" }]; nav[4].labels = [ { type: "warning", text: "Beta", tooltip: "Feature is still work in progress" }, { type: "highlight1", icon: <IconFeatureFlag />, tooltip: "Feature Flag Xyz" }, ]; nav[5].labels = [{ type: "highlight2", text: "Demo", tooltip: "Demo data" }]; return ( <> <div style={{ height: "35rem" }}> <NewSideNavigation data-observe-key="side-nav" mainItems={nav} bottomItems={[]} selectionId={selectionId} setSelectionId={setSelectionId} viewState={viewState} setViewState={setViewState} expandedItems={expandedItems} setExpandedItems={setExpandedItems} /> </div> </> );

This example has items with both href's but also onclick handlers. The idea is that simple clicks will be handled with the "onClick" handler and "preventDefault" so that the browser doesn't navigate. But if you "open in new tab" click, then the navigation happens.

  • Use Case: This pattern is useful when you need to trigger specific actions in addition to, or instead of, navigating to a new page.
  • Best Practice:
    • Clearly communicate to the user whether a click will result in navigation or a different action.
    • If the custom action fails, provide a fallback to ensure the user can still navigate to the destination.
Digital Certainty Index
Theme: agentic-ai-2025
// These states are kept outside the component because you would most likely want to persist // these in something like local storage to work well will page reloads. const [selectionId, setSelectionId] = useState<string | null>(null); const [viewState, setViewState] = useState<NewViewState>(); const [expandedItems, setExpandedItems] = useState<string[]>(); const items: MainNavigationItem[] = [ { children: [], defaultChildId: null, id: "dashboard", title: "Dashboards", href: "#", openNew: false, icon: <IconProductDashboard />, observeKey: "side-nav-dashboard", labels: [], visible: true, }, { children: [ { children: [], defaultChildId: null, id: "dci-overview", title: "DCI overview", href: "#", openNew: false, icon: <IconProductAds />, observeKey: "side-nav-dci-overview", labels: [], visible: true, }, { children: [], defaultChildId: null, id: "dci-account-overview", title: "My sites", href: "#", openNew: false, icon: <IconProductAds />, observeKey: "side-nav-dci-account-overview", labels: [], visible: true, }, { children: [], defaultChildId: null, id: "dci-sites-progress", title: "Sites progress", href: "#", openNew: false, icon: <IconProductAds />, observeKey: "side-nav-dci-sites-progress", labels: [ { type: "highlight1", icon: <IconFeatureFlag />, tooltip: "The feature is hidden behind the Feature Flag: RMDCISiteProgress", }, ], visible: true, }, ], defaultChildId: "dci-overview", id: "dci", title: "Digital Certainty Index", href: "#", openNew: false, icon: <IconProductDCI />, observeKey: "side-nav-dci", labels: [], visible: true, }, ]; return ( <> <div style={{ height: "35rem" }}> <NewSideNavigation data-observe-key="side-nav" mainItems={items} bottomItems={[]} selectionId={selectionId} setSelectionId={setSelectionId} viewState={viewState} setViewState={setViewState} expandedItems={expandedItems} setExpandedItems={setExpandedItems} /> </div> </> );

#One main nav item and fixed view

If there is only one main nav item in mainItems the sub nav will be shown on load and the "Back to main menu" button will be replaced by a back link that uses the props backLink and backLinkLabel. This can be combined with viewState="fixed" which will hide the view state toggle button to collapse and expand the navigation.

  • Use Case: This is perfect for applications with a single primary section and a fixed secondary navigation structure (E.g Page Inspector)
  • Best Practice: In this scenario, ensure the back link is clearly visible and labeled. Since the navigation is always visible, make sure it doesn't obscure or overlap with the main content area.
Theme: agentic-ai-2025
// These states are kept outside the component because you would most likely want to persist // these in something like local storage to work well will page reloads. const [selectionId, setSelectionId] = useState<string | null>(null); const [viewState, setViewState] = useState<NewViewState>("fixed"); const [expandedItems, setExpandedItems] = useState<string[]>(); return ( <> <div style={{ height: "75vh" }}> <NewSideNavigation data-observe-key="side-nav" mainItems={dataOneMainNavItem} bottomItems={[]} selectionId={selectionId} setSelectionId={setSelectionId} viewState={viewState} setViewState={setViewState} expandedItems={expandedItems} setExpandedItems={setExpandedItems} backButtonLabel="Back to main menu" backButtonUrl="http://www.siteimprove.com" /> </div> </> );

#Properties

#Guidelines

#Best practices

#General

Use SideNavigation for deep hierarchies or when users frequently switch sections.

#Placement

Place SideNavigation on the left (desktop). For smaller screen (e.g mobile), use collapsed view or Horizontal navigation.

#Style

  • Siteimprove Design System: Adhere to Siteimprove's guidelines for color, typography, and spacing. If you are not using a component from Fancy, match the styling of your SideNavigatio to existing components for visual consistency.
  • Limit navigation depth to three levels maximum.
  • Prioritize the most important navigation items at the top.
  • Match page titles to navigation link labels.

#Interaction

  • Use chevrons for expanding/collapsing nested levels.
  • Keep quaternary levels (4th level) as simple page links.
  • If more than five secondary items, consider using sub-menus.

#Responsive layout

  • Default to the expanded view.
  • Provide a collapse option with clear icons.
  • Auto-collapse for smaller screens (e.g. breakpoints of 768 pixels for tablet and mobile).

#Do not use when

  • Navigation is very simple (few top-level items).
  • Horizontal navigation is more suitable (e.g., system-level or shortcuts).

#Accessibility

#For designers

  • Ensure icon meanings are clear and labels are intuitive.

#For developers

  • Navigation link labels should be readable by assistive technologies, even when navigation is collapsed

Explore detailed guidelines for this component: Accessibility Specifications

#Writing

  • Use clear, concise labels under 20 characters to prevent text wrapping. Labels should be scannable yet descriptive.
  • Keep labels consistent in length, style (nouns or verbs), and sentence case.